
#!/usr/bin/env python

# (c) 2007-2011 Helsinki University of Technology and University of Helsinki
# Licensed under the MIT license; see LICENSE.txt for more information.

from Queue import Queue, Empty, Full
from threading import Thread
import sys
import traceback

class Worker:
  _EOW = []	# special end-of-work marker
  
  def __init__(self, fatalevent, maxqueue=20):
    self._fatalevent = fatalevent
    self._queue = Queue(maxqueue)
    self._name = str(self.__class__).split('.')[-1]
    self._done = 0
    self._thread = Thread(group=None, target=self._run, name=self._name)
    # make it a daemon thread, so the program stops if the main thread dies
    self._thread.setDaemon(True)
    self._thread.start()
  
  def addWork(self, item):
    while not self._fatalevent.isSet():
      try:
        self._queue.put(item, False, 1)
        break
      except Full:
        pass
  
  def shutdown(self):
    while not self._fatalevent.isSet():
      try:
        self._queue.put(self._EOW, True, 10)
        break
      except Full:
        pass
    
    self._thread.join()
  
  def getName(self):
    return self._name
  
  def getWorkWaiting(self):
    size = self._queue.qsize()
    if size > 0: return size - 1 # ignore EOW marker
    return 0
  
  def getWorkDone(self):
    return self._done
  
  def _run(self):
    try:
      self.initialize()
      while not self._fatalevent.isSet():
        try:
          item = self._queue.get(True, 10)
        except Empty:
          continue
        if item is self._EOW: # end-of-work marker reached
          if self._queue.qsize() > 0: 	# still something to do
            self.addWork(self._EOW) 	# move the marker behind waiting work
            continue 			# try to do more work
          self.finish()
          return	# let the thread exit
        successful = self.process(item)
        if successful:
          self._done += 1
    except:
      if sys is not None: # avoid interpreter shutdown problems
        print >>sys.stderr, "ERROR: Worker %s caught fatal exception:" % self._name
        traceback.print_exc()
        # signal fatal error to session thread
        self._fatalevent.set()

  # These should/may be overridden by subclasses that implement actual workers

  def process(self, item):
    print >>sys.stderr, "Dummy worker, processing:", repr(item)
  
  def initialize(self):
    pass
  
  def finish(self):
    pass
  
